<!doctype html>
<html>

<head>
    <meta charset="utf-8" />
    <title>整体风险情况</title>
    <!-- Antd reset and core css -->
    <link rel="stylesheet" href="{{ url_for('static', filename='vendor/css/reset.css') }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='vendor/css/antd.min.css') }}">
    <script src="{{ url_for('static', filename='vendor/js/react.development.js') }}"></script>
    <script src="{{ url_for('static', filename='vendor/js/react-dom.development.js') }}"></script>
    <script src="{{ url_for('static', filename='vendor/js/dayjs.min.js') }}"></script>
    <script src="{{ url_for('static', filename='vendor/js/antd.min.js') }}"></script>
    <script src="{{ url_for('static', filename='vendor/js/xlsx.full.min.js') }}"></script>
    <script src="{{ url_for('static', filename='vendor/js/babel.min.js') }}"></script>
    <style>
        .page-wrap { max-width: 1200px; min-width: 600px;margin: 24px auto; padding: 0 16px; }
        .page-title { font-size: 20px; font-weight: 600; margin-bottom: 16px; text-align: center; }

        /* 折叠面板头部 */
        .panel-header { display: flex; flex-direction: column; gap: 4px; margin-bottom: 4px; }
        .legend-row { display: flex; align-items: center; gap: 12px; justify-content: center; }
        .legend { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: #555; }
        .legend-square { width: 12px; height: 12px; display: inline-block; border-radius: 2px; }
        .legend-ng { background: red; }
        .legend-ok { background: green; }
        .legend-miss { background: grey; }

        .panel-title-row { display: flex; align-items: center; gap: 10px; }
        .collapse-btn { cursor: pointer; user-select: none; font-size: 14px; }
        .chart-icon { font-size: 14px; }
        .panel-title { font-size: 16px; font-weight: 600; }

        /* ColorBar 区域 */
        .colorbar-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1px; }
        .colorbar-card { padding: 12px; background: #fff; }
        .colorbar-title { font-weight: 600; margin-bottom: 8px; font-size: 14px; }
        .colorbar-bar { display: flex; align-items: center; gap: 10px; }
        .bar-track { flex: 0.7; height: 24px; background: #f5f5f5; border-radius: 0; overflow: hidden; display: flex; max-width: 600px; min-width: 200px; }
        .bar-ng { background: red; height: 100%; display: flex; align-items: center; justify-content: center; }
        .bar-ok { background: green; height: 100%; display: flex; align-items: center; justify-content: center; }
        .bar-miss { background: #d9d9d9; height: 100%; display: flex; align-items: center; justify-content: center; }
        /* 取消数值说明，比例由colorbar体现 */

        /* Tabs 与筛选 */
        .tabs { display: flex; margin-bottom: 12px; justify-content: center; }
        .tab-btn { padding: 6px 12px; border: 1px solid #91caff; color: #1677ff; background: #e6f4ff; cursor: pointer; }
        .tab-btn:first-child { border-radius: 6px 0 0 6px; }
        .tab-btn:last-child { border-radius: 0 6px 6px 0; }
        .tab-btn:not(:first-child) { border-left: none; }
        .tab-btn.active { background: #1677ff; color: #fff; border-color: #1677ff; }

        .filters { display: flex; align-items: center; gap: 16px; margin-bottom: 12px; }
        .filter-group { display: flex; align-items: center; gap: 10px; }
        .filter-sep { width: 150px; height: 16px; }
        .spacer { flex: 1; }
        .search-input { height: 30px; padding: 0 10px; border: 1px solid #d9d9d9; border-radius: 6px; width: 100px; }

        /* 项目ID 标签 */
        .project-label { font-size: 14px; font-weight: 600; margin-bottom: 12px; }
        /* 项目条：左侧项目ID，居中显示工段与状态（含 legend-square） */
        .project-bar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; margin-bottom: 12px; }
        .project-left { justify-self: start; font-size: 14px; font-weight: 600; }
        .project-center { justify-self: center; display: flex; align-items: center; gap: 12px; color: #555; }

        /* 表格样式 */
        .data-table { width: 100%; border-collapse: collapse; margin-bottom: 16px; }
        .data-table th, .data-table td { border: 1px solid #d9d9d9; padding: 8px 12px; text-align: left; }
        .data-table th { background: #f5f5f5; font-weight: 600; }

        /* 右下角按钮组 */
        .bottom-buttons { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
        .btn-primary { padding: 6px 16px; background: #1677ff; color: #fff; border: 1px solid #1677ff; border-radius: 6px; cursor: pointer; }
        .btn-primary:hover { background: #4096ff; }
        .btn-primary:disabled { background: #d9d9d9; color: #999; border-color: #d9d9d9; cursor: not-allowed; }

        /* 分页器样式 */
        .pagination { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 16px; padding: 12px 0; }
        .pagination-info { font-size: 14px; color: #666; margin-right: 16px; }
        .pagination-btn { padding: 4px 8px; border: 1px solid #d9d9d9; background: #fff; color: #333; border-radius: 4px; cursor: pointer; font-size: 12px; }
        .pagination-btn:hover:not(:disabled) { background: #f5f5f5; border-color: #1677ff; }
        .pagination-btn:disabled { background: #f5f5f5; color: #ccc; cursor: not-allowed; }
        .pagination-select { padding: 4px 8px; border: 1px solid #d9d9d9; background: #fff; color: #333; border-radius: 4px; cursor: pointer; font-size: 12px; min-width: 80px; }
        .pagination-select:hover { background: #f5f5f5; border-color: #1677ff; }
        .pagination-select:focus { outline: none; border-color: #1677ff; }

        /* Modal 样式 */
        .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
        .modal-content { background: #fff; border-radius: 8px; padding: 24px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; }
        .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
        .modal-title { font-size: 18px; font-weight: 600; }
        .modal-close { background: none; border: none; font-size: 20px; cursor: pointer; color: #666; }
        .modal-close:hover { color: #333; }
        .modal-row { display: flex; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #eee; }
        .modal-label { font-weight: 600; min-width: 100px; color: #333; }
        .modal-value { flex: 1; color: #666; word-wrap: break-word; word-break: break-all; white-space: pre-wrap; }
        .detail-link { color: #1677ff; text-decoration: none; cursor: pointer; }
        .detail-link:hover { text-decoration: underline; }
    </style>
</head>
<body>
    <div class="page-wrap">
        <div id="root"></div>
    </div>
    <!-- 你的应用脚本（如果使用 Babel 转译，type="text/babel"） -->
    <script type="text/babel">
        // 后端注入：七张表数组 [{ table_name, table_data }]
        const TABLES_LIST = {{ tables| tojson }};

        // 全局常量与工具
        const SECTION_KEY = { "测试段": "test", "前工序": "pre", "装配段": "assembly", "卷绕段": "winding" };
        const SECTION_LABEL = { test: '测试段', pre: '前工序', assembly: '装配段', winding: '卷绕段' };
        const STATUS_LABEL = { ng: 'NG项', ok: 'OK项', miss: '缺失项' };
        const COLORBAR_MIN_TOTAL = 1;          // 避免总数为 0 导致除零
        const TITLE_MIN_WIDTH_PX = 50;         // colorbar 标题最小宽度
        const XLSX_MIN_WCH = 8;                // 导出列的最小列宽字符数
        const XLSX_EXTRA_WCH = 2;              // 导出列宽额外留白

        /* 从原始行推导内部状态（优先使用 '状态'，无则使用 '风险评分'）*/
        const deriveStatusFromRaw = (rawRow) => {
            const s = rawRow?.["状态"];
            if (s === 'NG项') return { label: 'NG项', type: 'ng' };
            if (s === 'OK项') return { label: 'OK项', type: 'ok' };
            if (s === '缺失项') return { label: '缺失项', type: 'miss' };
            const score = rawRow?.['风险评分'];
            if (score === -1 || score === -1.0) return { label: 'NG项', type: 'ng' };
            if (score === 1 || score === 1.0) return { label: 'OK项', type: 'ok' };
            if (typeof score !== 'undefined') return { label: '缺失项', type: 'miss' };
            return { label: s || '', type: 'unknown' };
        };

        /*规范化为展示行（页面仅显示三列与详情链接）*/
        const normalizeToDisplayRow = (rawRow) => {
            const st = deriveStatusFromRaw(rawRow);
            return {
                "工段": rawRow["所属工段"],
                "全局工序名称": rawRow["全局工序名称"],
                "可制造名称": rawRow["可制造性名称"],
                "其他信息": '详情',
                _raw: rawRow,
                _status: st.label,    // 内部使用，不展示
                _type: st.type,       // ng/ok/miss/unknown
            };
        };

        // 构建：名称列表、按名称映射行、colorbar汇总
        const TABLE_NAMES_LIST = TABLES_LIST.map(t => t.table_name);
        const TABLE_ROWS = Object.fromEntries(
            TABLES_LIST.map(t => [t.table_name, (t.table_data || []).map(normalizeToDisplayRow)])
        );

        /**
         * 汇总 colorbar 数据。
         * 为什么：将多行数据聚合为各工段的 NG/OK/缺失 用量以渲染比例。
         */
        const summarize = (rows) => {
            const summary = {
                test: { ng: 0, ok: 0, miss: 0 },
                pre: { ng: 0, ok: 0, miss: 0 },
                assembly: { ng: 0, ok: 0, miss: 0 },
                winding: { ng: 0, ok: 0, miss: 0 },
            };
            rows.forEach(r => {
                const key = SECTION_KEY[r["工段"]];
                if (!key) return;
                const t = r._type;
                if (t === 'ng') summary[key].ng += 1;
                else if (t === 'ok') summary[key].ok += 1;
                else summary[key].miss += 1; // 包括 miss 与 unknown 统一计入
            });
            return summary;
        };

        const COLOR_BARS = Object.fromEntries(
            TABLE_NAMES_LIST.map(name => [name, summarize(TABLE_ROWS[name] || [])])
        );

        /**
         * 应用筛选与搜索。
         */
        const filterRows = (rows, filterState) => {
            if (!filterState || !filterState.sections || !filterState.types) return rows || [];
            const sectionAllowed = new Set(Object.entries(filterState.sections).filter(([, v]) => v).map(([k]) => k));
            const statusAllowed = new Set(Object.entries(filterState.types).filter(([, v]) => v).map(([k]) => k));
            const keyword = (filterState.keyword || '').trim();
            return (rows || []).filter(r => {
                const secKey = SECTION_KEY[r["工段"]];
                if (!sectionAllowed.has(secKey)) return false;
                if (r._type !== 'unknown' && !statusAllowed.has(r._type)) return false;
                if (!keyword) return true;
                const line = `${r["工段"]}|${r["全局工序名称"]}|${r["可制造名称"]}|${r._status}`;
                return line.includes(keyword);
            });
        };

        /**
         * 生成时间戳 YYYYMMDDHHmmss。
         */
        const formatTimestamp = (d = new Date()) => {
            const pad = (n) => String(n).padStart(2, '0');
            const y = d.getFullYear();
            const M = pad(d.getMonth() + 1);
            const day = pad(d.getDate());
            const h = pad(d.getHours());
            const m = pad(d.getMinutes());
            const s = pad(d.getSeconds());
            return `${y}${M}${day}${h}${m}${s}`;
        };

        /**
         * 导出多个工作表
         */
        const exportRawSheetsToXlsx = (rawRowsBySheet, filename = "风险评估报告.xlsx") => {
            if (!rawRowsBySheet || typeof rawRowsBySheet !== 'object') return;
            const wb = XLSX.utils.book_new();

            const makeHeaders = (rows) => {
                // 以首行的键顺序为基准，保持与传入相同的字段顺序；
                // 后续行若出现新键，则按出现顺序追加到末尾。
                let headers = [];
                const seen = new Set();
                if ((rows || []).length > 0) {
                    Object.keys(rows[0] || {}).forEach(k => { seen.add(k); headers.push(k); });
                }
                for (let i = 1; i < (rows || []).length; i++) {
                    const obj = rows[i] || {};
                    Object.keys(obj).forEach(k => { if (!seen.has(k)) { seen.add(k); headers.push(k); } });
                }
                // 如果存在“项目ID”，固定放到第一列
                if (headers.includes('项目ID')) {
                    headers = ['项目ID', ...headers.filter(h => h !== '项目ID')];
                }
                return headers;
            };

            Object.entries(rawRowsBySheet).forEach(([sheetName, rows]) => {
                const dataRows = Array.isArray(rows) ? rows : [];
                const headers = makeHeaders(dataRows);
                const ws = headers.length > 0
                    ? XLSX.utils.json_to_sheet(dataRows, { header: headers })
                    : XLSX.utils.aoa_to_sheet([["无数据"]]);
                if (headers.length > 0) {
                    ws['!cols'] = headers.map(h => ({ wch: Math.max(XLSX_MIN_WCH, String(h).length + XLSX_EXTRA_WCH) }));
                }
                XLSX.utils.book_append_sheet(wb, ws, String(sheetName).substring(0, 31));
            });
            try {
                XLSX.writeFile(wb, filename);
            } catch (e) {
                console.error('导出失败', e);
            }
        };
        {% raw %}
        // 根据原始行推断 legend CSS 类
        const getStatusClass = (raw) => {
            if (!raw) return '';
            // 兼容 normalized row 中的内部属性
            if (raw._type) {
                if (raw._type === 'ng') return 'legend-ng';
                if (raw._type === 'ok') return 'legend-ok';
                if (raw._type === 'miss') return 'legend-miss';
            }
            // 优先识别状态字段
            const s = raw['状态'] || raw['状态.'] || raw['state'] || raw['Status'] || raw._status || '';
            if (s === 'NG项' || s === 'NG' || s === 'ng' || String(s).toLowerCase().includes('ng')) return 'legend-ng';
            if (s === 'OK项' || s === 'OK' || s === 'ok' || String(s).toLowerCase().includes('ok')) return 'legend-ok';
            if (s === '缺失项' || s === '缺失' || s === 'miss' || String(s).toLowerCase().includes('miss')) return 'legend-miss';
            // 回退：识别风险评分字段
            const score = raw['风险评分'];
            if (score === -1 || score === -1.0) return 'legend-ng';
            if (score === 1 || score === 1.0) return 'legend-ok';
            if (typeof score !== 'undefined') return 'legend-miss';
            return '';
        };
        // 小图例组件
        const Legend = () => (
            <div className="legend-row">
                <span className="legend"><i className="legend-square legend-ng"></i>NG项</span>
                <span className="legend"><i className="legend-square legend-ok"></i>OK项</span>
                <span className="legend"><i className="legend-square legend-miss"></i>缺失项</span>
            </div>
        );

        // ColorBar 单卡片
        function ColorBarCard({ title, ng, ok, miss }) {
            const total = Math.max(ng + ok + miss, COLORBAR_MIN_TOTAL);
            const ngW = (ng / total) * 100;
            const okW = (ok / total) * 100;
            const missW = (miss / total) * 100;
            return (
                <div className="colorbar-card">
                    <div className="colorbar-bar">
                        <div className="colorbar-title" style={{ minWidth: `${TITLE_MIN_WIDTH_PX}px`, marginBottom: 0 }}>{title}: </div>
                        <div className="bar-track">
                            <div className="bar-ng" style={{ width: `${ngW}%` }}>
                                {ng > 0 && <span style={{ color: '#000', fontSize: '10px', fontWeight: 'bold' }}>{ng}</span>}
                            </div>
                            <div className="bar-ok" style={{ width: `${okW}%` }}>
                                {ok > 0 && <span style={{ color: '#000', fontSize: '10px', fontWeight: 'bold' }}>{ok}</span>}
                            </div>
                            <div className="bar-miss" style={{ width: `${missW}%` }}>
                                {miss > 0 && <span style={{ color: '#000', fontSize: '10px', fontWeight: 'bold' }}>{miss}</span>}
                            </div>
                        </div>
                    </div>
                </div>
            );
        }

        // 2x2 ColorBar 容器
        function ColorBarGrid({ data }) {
            return (
                <div className="colorbar-grid">
                    <ColorBarCard title="测试段" ng={data.test.ng} ok={data.test.ok} miss={data.test.miss} />
                    <ColorBarCard title="前工序" ng={data.pre.ng} ok={data.pre.ok} miss={data.pre.miss} />
                    <ColorBarCard title="装配段" ng={data.assembly.ng} ok={data.assembly.ok} miss={data.assembly.miss} />
                    <ColorBarCard title="卷绕段" ng={data.winding.ng} ok={data.winding.ok} miss={data.winding.miss} />
                </div>
            );
        }

        // 可折叠 Panel
        function CollapsiblePanel({ title, children, defaultOpen = false, showLegend = false }) {
            const [open, setOpen] = React.useState(defaultOpen);
            return (
                <div style={{ padding: 12, background: '#fff', marginBottom: 12 }}>
                    <div className="panel-header">
                        {showLegend && <Legend />}
                        <div className="panel-title-row">
                            <span className="collapse-btn" onClick={() => setOpen(v => !v)}>{open ? '▼' : '▶'}</span>
                            <span className="chart-icon">📊</span>
                            <span className="panel-title">{title}</span>
                        </div>
                    </div>
                    {open && (
                        <div>
                            {children}
                        </div>
                    )}
                </div>
            );
        }

        function Tabs({ tabs, activeKey, onChange }) {
            return (
                <div className="tabs">
                    {tabs.map(t => (
                        <button key={t.key} className={"tab-btn" + (t.key === activeKey ? " active" : "")} onClick={() => onChange(t.key)}>{t.label}</button>
                    ))}
                </div>
            );
        }

        function Filters({ state, onChange }) {
            const toggle = (group, key) => {
                onChange({ ...state, [group]: { ...state[group], [key]: !state[group][key] } });
            };
            return (
                <div className="filters">
                    <div className="filter-group">
                        <label><input type="checkbox" checked={state.sections.test} onChange={() => toggle('sections', 'test')} /> 测试段</label>
                        <label><input type="checkbox" checked={state.sections.pre} onChange={() => toggle('sections', 'pre')} /> 前工序</label>
                        <label><input type="checkbox" checked={state.sections.assembly} onChange={() => toggle('sections', 'assembly')} /> 装配段</label>
                        <label><input type="checkbox" checked={state.sections.winding} onChange={() => toggle('sections', 'winding')} /> 卷绕段</label>
                    </div>
                    <div className="filter-sep"></div>
                    <div className="filter-group">
                        <label><input type="checkbox" checked={state.types.ng} onChange={() => toggle('types', 'ng')} /> NG项</label>
                        <label><input type="checkbox" checked={state.types.ok} onChange={() => toggle('types', 'ok')} /> OK项</label>
                        <label><input type="checkbox" checked={state.types.miss} onChange={() => toggle('types', 'miss')} /> 缺失项</label>
                    </div>
                    <div className="spacer"></div>
                    <input className="search-input" placeholder="搜索" value={state.keyword} onChange={e => onChange({ ...state, keyword: e.target.value })} />
                </div>
            );
        }

        // Modal 组件
        function DetailModal({ isOpen, onClose, detailData }) {
            if (!isOpen) return null;
            const raw = detailData?.raw || {};
            // 使用外部定义的 helper 获取状态类，避免在 JSX 中使用 IIFE 导致模板冲突
            const statusCls = getStatusClass(raw);
            // 排除在主表中已展示/映射过的字段
            const EXCLUDE_KEYS = new Set([
                '所属工段', '全局工序名称', '可制造性名称',
                // 下面是可能的别名，防止重复
                '工段', '可制造名称', '状态', '其他信息'
            ]);
            const otherEntries = Object.entries(raw).filter(([k]) => !EXCLUDE_KEYS.has(k));

            return (
                <div className="modal-overlay" onClick={onClose}>
                    <div className="modal-content" onClick={e => e.stopPropagation()}>
                        <div className="modal-header">
                            <div className="modal-title">详细信息</div>
                            <button className="modal-close" onClick={onClose}>×</button>
                        </div>
                        {/* 顶部信息：加粗表名 + 工序（工段），无前缀文案 */}
                        <div className="modal-row">
                            <div className="modal-label" style={{ display: 'none' }}></div>
                            <div className="modal-value" style={{ paddingLeft: 0 }}>
                                {/* 单行：表名 + 空格 + 工段 + 空格 + 状态方块 */}
                                <span style={{ fontWeight: 700 }}>
                                    {detailData?.tableName || ''}
                                </span>
                                <span style={{ margin: '0 8px' }}>{detailData?.section || ''}</span>
                                {statusCls && <i className={`legend-square ${statusCls}`} style={{ display: 'inline-block', verticalAlign: 'middle' }}></i>}
                            </div>
                        </div>
                        {otherEntries.length === 0 ? (
                            <div className="modal-row">
                                <div className="modal-label">其他信息:</div>
                                <div className="modal-value">无</div>
                            </div>
                        ) : (
                            otherEntries.map(([k, v], i) => (
                                <div className="modal-row" key={i}>
                                    <div className="modal-label">{k} </div>
                                    <div className="modal-value">{String(v)}</div>
                                </div>
                            ))
                        )}
                    </div>
                </div>
            );
        }

        // 分页器组件
        function Pagination({ currentPage, totalPages, totalItems, pageSize, onPageChange }) {
            // 生成分页选项
            const getPageOptions = () => {
                const options = [];
                for (let i = 1; i <= totalPages; i++) {
                    const startItem = (i - 1) * pageSize + 1;
                    const endItem = Math.min(i * pageSize, totalItems);
                    options.push({
                        value: i,
                        label: `第${startItem}-${endItem}条`
                    });
                }
                return options;
            };

            const pageOptions = getPageOptions();
            const currentOption = pageOptions.find(opt => opt.value === currentPage);

            return (
                <div className="pagination">
                    <span className="pagination-info">共{totalItems}条</span>
                    <button
                        className="pagination-btn"
                        disabled={currentPage === 1}
                        onClick={() => onPageChange(currentPage - 1)}
                    >
                        上一页
                    </button>
                    <select
                        className="pagination-select"
                        value={currentPage}
                        onChange={(e) => onPageChange(parseInt(e.target.value))}
                    >
                        {pageOptions.map(option => (
                            <option key={option.value} value={option.value}>
                                {option.label}
                            </option>
                        ))}
                    </select>
                    <button
                        className="pagination-btn"
                        disabled={currentPage === totalPages}
                        onClick={() => onPageChange(currentPage + 1)}
                    >
                        下一页
                    </button>
                </div>
            );
        }

        // 表格组件（Tabs 区域专用）：固定显示三列 + 详情
        function DataTable({ rows, onOpenDetail, activeTableName }) {
            return (
                <table className="data-table">
                    <thead>
                        <tr>
                            <th>工段</th>
                            <th>全局工序名称</th>
                            <th>可制造性名称</th>
                            <th>其他信息</th>
                        </tr>
                    </thead>
                    <tbody>
                        {(rows || []).map((row, idx) => (
                            <tr key={idx}>
                                <td>{row["工段"]}</td>
                                <td>{row["全局工序名称"]}</td>
                                <td>{row["可制造名称"]}</td>
                                <td>
                                    <span
                                        className="detail-link"
                                        onClick={() => onOpenDetail({ tableName: activeTableName, section: row["工段"], raw: row._raw || {} })}
                                    >
                                        详情
                                    </span>
                                </td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            );
        }

        function App() {
            // 默认激活第一张表
            const [active, setActive] = React.useState(TABLE_NAMES_LIST[0] || '');
            const [modalOpen, setModalOpen] = React.useState(false);
            const [modalData, setModalData] = React.useState(null);

            // 分页配置
            const PAGE_SIZE = 20;
            
            // 为每个表维护独立筛选状态和分页状态
            const makeDefaultFilter = () => ({
                sections: { test: true, pre: true, assembly: true, winding: true },
                types: { ng: true, ok: false, miss: false },
                keyword: '',
            });
            const makeDefaultPagination = () => ({ currentPage: 1 });
            
            const initialFilters = Object.fromEntries((TABLE_NAMES_LIST).map(n => [n, makeDefaultFilter()]));
            const initialPagination = Object.fromEntries((TABLE_NAMES_LIST).map(n => [n, makeDefaultPagination()]));
            
            const [filters, setFilters] = React.useState(initialFilters);
            const [pagination, setPagination] = React.useState(initialPagination);
            
            const currentFilter = filters[active];
            const currentPagination = pagination[active];
            
            const setFilterForActive = (next) => setFilters(prev => ({ ...prev, [active]: next }));
            const setPaginationForActive = (next) => setPagination(prev => ({ ...prev, [active]: next }));

            const tabs = (TABLE_NAMES_LIST).map(n => ({ key: n, label: n }));

            // 使用顶部常量/工具

            const currentRows = React.useMemo(() => (TABLE_ROWS[active] || []), [active]);
            const filteredRows = React.useMemo(() => filterRows(currentRows, currentFilter), [currentRows, currentFilter]);
            
            // 分页处理
            const totalItems = filteredRows.length;
            const totalPages = Math.ceil(totalItems / PAGE_SIZE);
            const startIndex = (currentPagination.currentPage - 1) * PAGE_SIZE;
            const endIndex = startIndex + PAGE_SIZE;
            const paginatedRows = filteredRows.slice(startIndex, endIndex);
            
            // 当筛选条件改变时，重置到第一页
            React.useEffect(() => {
                if (currentPagination.currentPage > totalPages && totalPages > 0) {
                    setPaginationForActive({ currentPage: 1 });
                }
            }, [totalPages, currentPagination.currentPage]);

            // 组装“工段/状态”显示文本和中部 legend 区域
            const selectedSections = React.useMemo(() => {
                const selected = Object.entries(currentFilter.sections).filter(([, v]) => v).map(([k]) => k);
                return selected.length === 4 ? '全部' : selected.map(k => SECTION_LABEL[k]).join('、');
            }, [currentFilter.sections]);
            // 已移除未被使用的 selectedStatuses，避免无用计算
            const centerLegendItems = React.useMemo(() => {
                const items = [];
                if (currentFilter.types.ng) items.push({ cls: 'legend-ng', label: 'NG项' });
                if (currentFilter.types.ok) items.push({ cls: 'legend-ok', label: 'OK项' });
                if (currentFilter.types.miss) items.push({ cls: 'legend-miss', label: '缺失项' });
                return items;
            }, [currentFilter.types]);

            // 下载当前/全部
            const onDownloadCurrent = () => {
                // 按“所有表”的当前筛选状态导出各自原始数据行，不限制字段
                const rowsBySheet = Object.fromEntries(
                    TABLE_NAMES_LIST.map(name => {
                        const rows = TABLE_ROWS[name] || [];
                        const filter = filters[name];
                        const filtered = filter ? filterRows(rows, filter) : rows;
                        return [name, filtered.map(r => r._raw || {})];
                    })
                );
                const ts = formatTimestamp();
                exportRawSheetsToXlsx(rowsBySheet, `风险评估报告_${ts}.xlsx`);
            };

            const onDownloadAll = () => {
                // 导出"原始JSON全部表全部字段"，不受筛选影响
                const allRaw = Object.fromEntries(
                    TABLES_LIST.map(t => [t.table_name, Array.isArray(t.table_data) ? t.table_data : []])
                );
                exportRawSheetsToXlsx(allRaw, `风险评估完整报告.xlsx`);
            };

            // 分页器事件处理
            const handlePageChange = (page) => {
                setPaginationForActive({ currentPage: page });
            };

            return (
                <div>
                    {/* section: 整体风险情况 */}
                    <section id="section_risk_overview">
                        <div className="page-title">整体风险情况</div>
                        {/* 图例只显示一次 */}
                        <div style={{ marginBottom: '16px' }}>
                            <Legend />
                        </div>
                        {/* 数据表的概览colorbar */}
                        {(TABLE_NAMES_LIST).map((name) => (
                            <CollapsiblePanel key={name} title={name} showLegend={false}>
                                <ColorBarGrid data={COLOR_BARS[name]} />
                            </CollapsiblePanel>
                        ))}
                    </section>

                    {/* section: 风险评估报告 */}
                    <section id="section_risk_assessments" style={{ margin: '24px' }}>
                        <div className="page-title">风险评估报告</div>
                        <Tabs tabs={tabs} activeKey={active} onChange={setActive} />
                        <Filters state={currentFilter} onChange={setFilterForActive} />
                        <div className="bottom-buttons">
                            <button className="btn-primary" onClick={onDownloadCurrent}>下载当前报告</button>
                            <button className="btn-primary" onClick={onDownloadAll}>下载完整表格</button>
                        </div>

                        <div style={{ marginTop: '16px' }}>
                            <div className="project-bar">
                                <div className="project-left">项目ID: {active}</div>
                                <div className="project-center">
                                    <span>{selectedSections}</span>
                                    <span>|</span>
                                    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
                                        {centerLegendItems.map((it, i) => (
                                            <span key={i} style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
                                                <i className={`legend-square ${it.cls}`}></i>
                                                <span>{it.label}</span>
                                            </span>
                                        ))}
                                    </span>
                                </div>
                                <div></div>
                            </div>
                            <DataTable
                                rows={paginatedRows}
                                activeTableName={active}
                                onOpenDetail={(payload) => { setModalData(payload); setModalOpen(true); }}
                            />
                            {totalPages > 1 && (
                                <Pagination
                                    currentPage={currentPagination.currentPage}
                                    totalPages={totalPages}
                                    totalItems={totalItems}
                                    pageSize={PAGE_SIZE}
                                    onPageChange={handlePageChange}
                                />
                            )}
                        </div>
                    </section>

                    {/* Modal 组件 */}
                    <DetailModal
                        isOpen={modalOpen}
                        onClose={() => setModalOpen(false)}
                        detailData={modalData}
                    />
                </div>
            );
        }

        ReactDOM.createRoot(document.getElementById('root')).render(<App />);
        {% endraw %}
    </script>
</body>

</html>